"""
Unit tests for core data models (Bar, Tick, Orderbook, etc.)
"""

import pytest
from decimal import Decimal
from datetime import datetime, timezone

from app.adapters.core import (
    Bar, Tick, Orderbook, OrderbookLevel,
    DataFrequency, TickDirection, MarketType, Exchange,
    parse_symbol, build_symbol
)


class TestBar:
    """Tests for Bar model"""
    
    def test_create_valid_bar(self):
        """Test creating a valid bar"""
        bar = Bar(
            symbol="000001.SZ",
            timestamp=datetime(2024, 1, 1, 9, 30, tzinfo=timezone.utc),
            open=Decimal("10.00"),
            high=Decimal("10.50"),
            low=Decimal("9.90"),
            close=Decimal("10.20"),
            volume=Decimal("1000000"),
            amount=Decimal("10150000"),
            frequency=DataFrequency.DAILY,
            market_type=MarketType.STOCK,
            exchange=Exchange.SZSE,
        )
        
        assert bar.symbol == "000001.SZ"
        assert bar.open == Decimal("10.00")
        assert bar.high == Decimal("10.50")
        assert bar.low == Decimal("9.90")
        assert bar.close == Decimal("10.20")
        assert bar.volume == Decimal("1000000")
    
    def test_bar_validation(self):
        """Test bar validation logic"""
        # Test invalid high/low
        with pytest.raises(ValueError, match="high.*greater.*low"):
            Bar(
                symbol="000001.SZ",
                timestamp=datetime.now(timezone.utc),
                open=Decimal("10.00"),
                high=Decimal("9.00"),  # high < low
                low=Decimal("10.00"),
                close=Decimal("10.00"),
                volume=Decimal("1000"),
                frequency=DataFrequency.DAILY,
            )
        
        # Test negative volume
        with pytest.raises(ValueError, match="non-negative"):
            Bar(
                symbol="000001.SZ",
                timestamp=datetime.now(timezone.utc),
                open=Decimal("10.00"),
                high=Decimal("10.50"),
                low=Decimal("9.90"),
                close=Decimal("10.00"),
                volume=Decimal("-1000"),  # negative
                frequency=DataFrequency.DAILY,
            )
    
    def test_bar_computed_properties(self):
        """Test computed properties of bar"""
        bar = Bar(
            symbol="000001.SZ",
            timestamp=datetime.now(timezone.utc),
            open=Decimal("10.00"),
            high=Decimal("10.50"),
            low=Decimal("9.90"),
            close=Decimal("10.20"),
            volume=Decimal("1000"),
            frequency=DataFrequency.DAILY,
        )
        
        # Test is_bullish
        assert bar.is_bullish is True
        
        # Test amplitude
        assert bar.amplitude == Decimal("0.60")
        
        # Test change
        assert bar.change == Decimal("0.20")
        
        # Test change_percent
        assert bar.change_percent == Decimal("2.00")
    
    def test_bar_to_influx_point(self):
        """Test conversion to InfluxDB point"""
        bar = Bar(
            symbol="000001.SZ",
            timestamp=datetime(2024, 1, 1, 9, 30, tzinfo=timezone.utc),
            open=Decimal("10.00"),
            high=Decimal("10.50"),
            low=Decimal("9.90"),
            close=Decimal("10.20"),
            volume=Decimal("1000000"),
            frequency=DataFrequency.DAILY,
            market_type=MarketType.STOCK,
            exchange=Exchange.SZSE,
        )
        
        point = bar.to_influx_point()
        
        assert 'time' in point
        assert point['tags']['symbol'] == "000001.SZ"
        assert point['tags']['frequency'] == "1d"
        assert point['fields']['open'] == 10.00
        assert point['fields']['high'] == 10.50
        assert point['fields']['low'] == 9.90
        assert point['fields']['close'] == 10.20


class TestTick:
    """Tests for Tick model"""
    
    def test_create_valid_tick(self):
        """Test creating a valid tick"""
        tick = Tick(
            symbol="000001.SZ",
            timestamp=datetime.now(timezone.utc),
            price=Decimal("10.50"),
            volume=Decimal("1000"),
            direction=TickDirection.BUY,
            market_type=MarketType.STOCK,
            exchange=Exchange.SZSE,
        )
        
        assert tick.symbol == "000001.SZ"
        assert tick.price == Decimal("10.50")
        assert tick.volume == Decimal("1000")
        assert tick.direction == TickDirection.BUY
    
    def test_tick_to_influx_point(self):
        """Test conversion to InfluxDB point"""
        tick = Tick(
            symbol="000001.SZ",
            timestamp=datetime(2024, 1, 1, 9, 30, 0, tzinfo=timezone.utc),
            price=Decimal("10.50"),
            volume=Decimal("1000"),
            direction=TickDirection.BUY,
            market_type=MarketType.STOCK,
            exchange=Exchange.SZSE,
        )
        
        point = tick.to_influx_point()
        
        assert 'time' in point
        assert point['tags']['symbol'] == "000001.SZ"
        assert point['tags']['direction'] == "buy"
        assert point['fields']['price'] == 10.50
        assert point['fields']['volume'] == 1000.0


class TestOrderbook:
    """Tests for Orderbook model"""
    
    def test_create_valid_orderbook(self):
        """Test creating a valid orderbook"""
        orderbook = Orderbook(
            symbol="000001.SZ",
            timestamp=datetime.now(timezone.utc),
            bids=[
                OrderbookLevel(price=Decimal("10.00"), volume=Decimal("1000")),
                OrderbookLevel(price=Decimal("9.99"), volume=Decimal("2000")),
            ],
            asks=[
                OrderbookLevel(price=Decimal("10.01"), volume=Decimal("1500")),
                OrderbookLevel(price=Decimal("10.02"), volume=Decimal("2500")),
            ],
            market_type=MarketType.STOCK,
            exchange=Exchange.SZSE,
        )
        
        assert len(orderbook.bids) == 2
        assert len(orderbook.asks) == 2
        assert orderbook.bids[0].price == Decimal("10.00")
        assert orderbook.asks[0].price == Decimal("10.01")
    
    def test_orderbook_computed_properties(self):
        """Test computed properties of orderbook"""
        orderbook = Orderbook(
            symbol="000001.SZ",
            timestamp=datetime.now(timezone.utc),
            bids=[
                OrderbookLevel(price=Decimal("10.00"), volume=Decimal("1000")),
                OrderbookLevel(price=Decimal("9.99"), volume=Decimal("2000")),
            ],
            asks=[
                OrderbookLevel(price=Decimal("10.01"), volume=Decimal("1500")),
                OrderbookLevel(price=Decimal("10.02"), volume=Decimal("2500")),
            ],
        )
        
        assert orderbook.best_bid == Decimal("10.00")
        assert orderbook.best_ask == Decimal("10.01")
        assert orderbook.spread == Decimal("0.01")
        assert orderbook.mid_price == Decimal("10.005")


class TestSymbolParsing:
    """Tests for symbol parsing utilities"""
    
    def test_parse_symbol(self):
        """Test symbol parsing"""
        code, exchange = parse_symbol("000001.SZ")
        assert code == "000001"
        assert exchange == "SZ"
        
        code, exchange = parse_symbol("600000.SH")
        assert code == "600000"
        assert exchange == "SH"
        
        # Without exchange
        code, exchange = parse_symbol("000001")
        assert code == "000001"
        assert exchange is None
    
    def test_build_symbol(self):
        """Test symbol building"""
        assert build_symbol("000001", "SZ") == "000001.SZ"
        assert build_symbol("600000", "SH") == "600000.SH"
        assert build_symbol("000001", None) == "000001"
        assert build_symbol("000001", "") == "000001"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])

